﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
using ZeroBug.EasyFlow.Communication;
using ZeroBug.EasyFlow.Core;
using static System.Runtime.InteropServices.JavaScript.JSType;

namespace ZeroBug.EasyFlow.Communication
{
    public class CommunicationClient : IMachineResource<ICommunicationClient>, IResourceVariable
    {
        private ICommunicationClient? _commOperator;
        private object sendLock = new object();
        private object packageLock = new object();
        private List<SendPackage<string>> _sendPackagesStr;
        private List<SendPackage<byte[]>> _sendPackagesByte;
        public string? Description { get; set; }
        public string? OperatorName { get; set; }
        public CommunicationConfig? Config { get; set; }
        [XmlIgnore]
        public ICommClientState? State { get; set; }
        [XmlIgnore]
        public byte[]? ReceivedBytes { get; set; }
        [XmlIgnore]
        public string? ReceivedString { get; set; }
        [XmlIgnore]
        public Encoding SendEncoding
        {
            get
            {
                Encoding encoding = Encoding.Default;
                switch (Config.SendEncoding)
                {
                    case EncodingEnumStr.ASCII:
                        encoding = Encoding.ASCII;
                        break;
                    case EncodingEnumStr.Default:
                        encoding = Encoding.Default;
                        break;
                    case EncodingEnumStr.UTF8:
                        encoding = Encoding.UTF8;
                        break;
                    case EncodingEnumStr.UTF32:
                        encoding = Encoding.UTF32;
                        break;
                    case EncodingEnumStr.Unicode:
                        encoding = Encoding.Unicode;
                        break;
                }
                return encoding;
            }
        }
        [XmlIgnore]
        public Encoding ReceiveEncoding
        {
            get
            {
                Encoding encoding = Encoding.Default;
                switch (Config.ReceiveEncoding)
                {
                    case EncodingEnumStr.ASCII:
                        encoding = Encoding.ASCII;
                        break;
                    case EncodingEnumStr.Default:
                        encoding = Encoding.Default;
                        break;
                    case EncodingEnumStr.UTF8:
                        encoding = Encoding.UTF8;
                        break;
                    case EncodingEnumStr.UTF32:
                        encoding = Encoding.UTF32;
                        break;
                    case EncodingEnumStr.Unicode:
                        encoding = Encoding.Unicode;
                        break;
                }
                return encoding;
            }
        }
        [XmlIgnore]
        public LogHelper? Logger { get; set; }
        [XmlIgnore]
        public CommErrorEventArgs? LastError { get; set; }
        public event EventHandler<string>? OnClientConnected;
        public event EventHandler<string>? OnClientDisconnected;
        public event EventHandler<string>? OnClientReceived;
        public CommunicationClient()
        {
            _sendPackagesStr = new List<SendPackage<string>>();
            _sendPackagesByte = new List<SendPackage<byte[]>>();
            State = new TCPClientCommState();
        }
        public void SetOperator(ICommunicationClient machineOperator)
        {
            _commOperator = machineOperator;
            _commOperator.OnErr += commOperator_OnErr;
            _commOperator.OnDatagramReceived += commOperator_OnDatagramReceived;
        }

        private void commOperator_OnErr(object? sender, CommErrorEventArgs e)
        {
            LastError = e;
        }

        string lastReceivedString = string.Empty;
        byte[] lastReceivedBytes;
        private void commOperator_OnDatagramReceived(object? sender, DataReceivedEventArgs<byte[]> e)
        {
            ReceivedBytes = e.Datagram;
            ReceivedString = ReceiveEncoding.GetString(e.Datagram);

            if (_sendPackagesStr != null && _sendPackagesStr.Count > 0)
            {
                string total = lastReceivedString + ReceivedString;
                if (!string.IsNullOrEmpty(total))
                {
                    string result = string.Empty;
                    string leftStr = string.Empty;
                    SendPackage<string>? sp = _sendPackagesStr.FirstOrDefault(sp => IsMatch(total, sp.StartPattern, sp.EndPattern, out result, out leftStr));
                    if (sp != null)
                    {
                        sp.ReceivedData = result;
                        sp.ReceivingFlag = true;
                        lastReceivedString = leftStr;
                    }
                    else
                        lastReceivedString = total;
                }
            }
            if(_sendPackagesByte != null && _sendPackagesByte.Count > 0)
            {
                byte[] total = null;
                if (lastReceivedBytes != null)
                    total = lastReceivedBytes.Concat(ReceivedBytes).ToArray();
                else
                {
                    total = new byte[ReceivedBytes.Length];
                    Buffer.BlockCopy(ReceivedBytes, 0, total, 0, total.Length);
                }
                byte[] result = null;
                byte[] leftBytes = null;
                SendPackage<byte[]>? sp = _sendPackagesByte.FirstOrDefault(sp => IsMatch(total, sp.StartPattern, sp.EndPattern, out result, out leftBytes));
                if(sp != null)
                {
                    sp.ReceivedData = result;
                    sp.ReceivingFlag = true;
                    lastReceivedBytes = leftBytes;
                }
                else
                    lastReceivedBytes = total;
            }
        }
        public bool IsMatch(string input, string startPattern, string endPattern, out string result, out string leftStr)
        {
            int startIndex = 0;
            int endIndex = input.Length;
            result = string.Empty;
            leftStr = string.Empty;
            // 截取开始部分
            while (startIndex < input.Length && !input.Substring(startIndex).StartsWith(startPattern))
            {
                startIndex++;
            }

            // 截取结束部分
            while (endIndex > startIndex && !input.Substring(0, endIndex).EndsWith(endPattern))
            {
                endIndex--;
            }

            // 如果没有找到匹配的字符串，则返回原始字符串
            if (startIndex >= endIndex)
            {
                leftStr = input;
                return false;
            }
            result = input.Substring(startIndex, endIndex - startIndex);
            leftStr = input.Substring(input.IndexOf(result) + result.Length);
            return true;
        }
        public bool IsMatch(byte[] input, byte[] startPattern, byte[] endPattern, out byte[] result, out byte[] leftBytes)
        {
            int startIndex = 0;
            int endIndex = input.Length;
            // 截取开始部分
            while (startIndex < input.Length && !input.Take(new Range(startIndex, startIndex + startPattern.Length)).SequenceEqual(startPattern))
            {
                startIndex++;
            }
            // 截取结束部分
            while (endIndex > startIndex && !input.Take(new Range(endIndex - endPattern.Length, endIndex)).SequenceEqual(endPattern))
            {
                endIndex--;
            }

            // 如果没有找到匹配的字符串，则返回原始字符串
            if (startIndex >= endIndex)
            {
                leftBytes = new byte[input.Length];
                Buffer.BlockCopy(input, 0, leftBytes, 0, leftBytes.Length);
                result = null;
                return false;
            }
            result = new byte[endIndex - startIndex];
            Buffer.BlockCopy(input, startIndex, result, 0, result.Length);
            if (endIndex == input.Length)
                leftBytes = null;
            else
            {
                leftBytes = new byte[input.Length - endIndex];
                Buffer.BlockCopy(input, endIndex, leftBytes, 0, leftBytes.Length);
            }
            return true;
        }
        public bool? Connect()
        {
            bool? result = false;
            result = _commOperator!.Connect(Config, State);
            if (result == true)
            {
                OnClientConnected?.Invoke(this, (State as TCPClientCommState)!.EndPoint);
                Logger!.Log(LogSource.Pluggin, $"Connect [{(Config as TCPClientConfig)!.IpAddress}:{(Config as TCPClientConfig)!.Port}] success", MessageLevel.Log);
            }
            else
                Logger!.Log(LogSource.Pluggin, $"Connect [{(Config as TCPClientConfig)!.IpAddress}:{(Config as TCPClientConfig)!.Port}] fail: {LastError?.ErrInfo}", MessageLevel.Error, LastError?.Err);
            return result;
        }
        public bool Close()
        {
            bool result = false;
            string ep = (State as TCPClientCommState)!.EndPoint;
            result = _commOperator!.Disconnect(State);
            if (result == true)
            {
                OnClientDisconnected?.Invoke(this, ep);
                Logger!.Log(LogSource.Pluggin, $"Disconnect [{(Config as TCPClientConfig)!.IpAddress}:{(Config as TCPClientConfig)!.Port}] success", MessageLevel.Log);
            }
            else
                Logger!.Log(LogSource.Pluggin, $"Disconnect [{(Config as TCPClientConfig)!.IpAddress}:{(Config as TCPClientConfig)!.Port}] fail", MessageLevel.Log);
            _commOperator.OnErr -= commOperator_OnErr;
            _commOperator.OnDatagramReceived -= commOperator_OnDatagramReceived;
            return result;
        }
        public void Send<T>(SendPackage<T> sendPackage)
        {
            if (Connect() == true)
            {
                lock (sendLock)
                {
                    bool? result = false;
                    sendPackage.ReceivingFlag = false;
                    if (sendPackage.SendRule != null)
                    {
                        if (typeof(T) == typeof(string))
                            result = _commOperator?.Send(State, sendPackage.SendRule(sendPackage.Message) as string, SendEncoding);
                        else
                            result = _commOperator?.Send(State, sendPackage.SendRule(sendPackage.Message) as byte[]);
                        if (result == true)
                            Logger!.Log(LogSource.Pluggin, $"Send to [{(Config as TCPClientConfig)!.IpAddress}:{(Config as TCPClientConfig)!.Port}] {sendPackage.SendRule(sendPackage.Message)}", MessageLevel.Log);
                    }
                    else
                    {
                        if (typeof(T) == typeof(string))
                            result = _commOperator?.Send(State, sendPackage.Message as string, SendEncoding);
                        else
                            result = _commOperator?.Send(State, sendPackage.Message as byte[]);
                        if (result == true)
                            Logger!.Log(LogSource.Pluggin, $"Send to [{(Config as TCPClientConfig)!.IpAddress}:{(Config as TCPClientConfig)!.Port}] {sendPackage.Message}", MessageLevel.Log);
                    }
                    if (result == false)
                        Logger!.Log(LogSource.Pluggin, $"Send to [{(Config as TCPClientConfig)!.IpAddress}:{(Config as TCPClientConfig)!.Port}] Fail {LastError.ErrInfo}", MessageLevel.Error, LastError.Err);
                    lock (packageLock)
                    {
                        if (typeof(T) == typeof(string))
                            _sendPackagesStr.Add(sendPackage as SendPackage<string>);
                        else
                            _sendPackagesByte.Add(sendPackage as SendPackage<byte[]>);
                    }
                }
            }
        }
        public void Remove<T>(SendPackage<T> sendPackage)
        {
            if (typeof(T) == typeof(string))
                _sendPackagesStr.Remove(sendPackage as SendPackage<string>);
            else
                _sendPackagesByte.Remove(sendPackage as SendPackage<byte[]>);
        }
        public void EmptyBuffer()
        {
            lock (packageLock)
            {
                _sendPackagesByte.Clear();
                _sendPackagesStr.Clear();
            }
            lastReceivedString = string.Empty;
            lastReceivedBytes = null;
        }
    }
}
